//
//  TimingTests.swift
//  Clock Signal
//
//  Created by Thomas Harte on 13/08/2015.
//  Copyright 2015 Thomas Harte. All rights reserved.
//

import Foundation
import XCTest

class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler {

	private var endTime: UInt32 = 0
	private let machine = CSTestMachine6502(processor: .processor6502)

	func testImplied() {
		let code: [UInt8] = [
			0xea,		// [2] NOP
			0x88,		// [2] DEY
			0xca,		// [2] DEX
			0x18,		// [2] CLC
			0x2a,		// [2] ROL A
		]
		runTest(code, expectedRunLength: 10)
	}

	func testLDA() {
		let code: [UInt8] = [
			0xa9, 0x00,			// [2] LDA #$00
			0xa5, 0x00,			// [3] LDA $00
			0xb5, 0x00,			// [4] LDA $00,X
			0xad, 0x00, 0x00,	// [4] LDA $0000
			0xbd, 0x00, 0x00,	// [4] LDA $0000, x (no wrap)
			0xbd, 0x02, 0x00,	// [5] LDA $0002, x (wrap)
			0xb9, 0x00, 0x00,	// [4] LDA $0000, y (no wrap)

			0xb9, 0x10, 0x00,	// [5] LDA $0010, y (wrap)
			0xa1, 0x44,			// [6] LDA ($44, x)
			0xb1, 0x00,			// [5] LDA ($00), y (no wrap)
			0xb1, 0x02,			// [6] LDA ($01), y (wrap)
		]
		runTest(code, expectedRunLength: 48)
	}

	func testBIT() {
		let code: [UInt8] = [
			0x24, 0x2a,			// [3] BIT $2a
			0x2c, 0x2a, 0x2b,	// [4] BIT $2b2a
		]
		runTest(code, expectedRunLength: 7)
	}

	func testSTA() {
		let code: [UInt8] = [
			0x85, 0x00,			// [3] STA $00
			0x95, 0x00,			// [4] STA $00,X
			0x8d, 0x00, 0x00,	// [4] STA $0000
			0x9d, 0x00, 0x00,	// [5] STA $0000, x (no wrap)
			0x9d, 0x02, 0x00,	// [5] STA $0002, x (wrap)
			0x99, 0x00, 0x00,	// [5] STA $0000, y (no wrap)
			0x99, 0x10, 0x00,	// [5] STA $0010, y (wrap)
			0x81, 0x44,			// [6] STA ($44, x)
			0x91, 0x00,			// [6] STA ($00), y (no wrap)
			0x91, 0x02,			// [6] STA ($01), y (wrap)
		]
		runTest(code, expectedRunLength: 49)
	}

	func testINC() {
		let code: [UInt8] = [
			0xe6, 0x00,			// [5] INC $00
			0xf6, 0x00,			// [6] INC $00,X
			0xee, 0x00, 0x00,	// [6] INC $0000
			0xfe, 0x00, 0x00,	// [7] INC $0000, x (no wrap)
			0xfe, 0x02, 0x00,	// [7] INC $0002, x (wrap)
		]
		runTest(code, expectedRunLength: 31)
	}

	func testJSR() {
		let code: [UInt8] = [
			0x20, 0x04, 0x02,	// [6] JSR $0204
			0x00,
			0x60,				// [6] RTS
		]
		machine.addTrapAddress(0x0203)
		runTest(code, expectedRunLength: 12)
	}

	func testJMP() {
		let code: [UInt8] = [
			0x6c, 0x04, 0x00,	// [5] JMP ($0004)
			0x00, 0x00, 0x00, 0x00, 0x00,
			0x4c, 0x0b, 0x02,	// [3] JMP 020b
		]
		runTest(code, expectedRunLength: 8)
	}

	func testPHAPLA() {
		let code: [UInt8] = [
			0x48, // [3] PHA
			0x48, // [3] PHA
			0x68, // [4] PLA
		]
		runTest(code, expectedRunLength: 10)
	}

	func testBCS() {
		let code: [UInt8] = [
			0x18,				// [2] CLC
			0xb0, 0xff,			// [2] BCS -1 (not taken)
			0x90, 0x7f,			// [3] BCC +7f (taken)
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x90, 0x00,			// [3] BCC 0 (taken)
			0x90, 0x7f,			// [4] BCC +6f (taken, page wrap)
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		]
		runTest(code, expectedRunLength: 14)
	}

	func testSnippet1() {
		let code: [UInt8] = [
			0x8d, 0x08, 0x00,	// [4] STA $0008
			0xc6, 0xb4,			// [5] DEC $B4
		]
		runTest(code, expectedRunLength: 9)
	}

	func testSnippet2() {
		let code: [UInt8] = [
			0x16, 0x16,			// [6] ASL $16, x
			0x46, 0x46,			// [5] LSR $46
		]
		runTest(code, expectedRunLength: 11)
	}

	func testSnippet3() {
		let code: [UInt8] = [
			0x20, 0x04, 0x02,	// [6] JSR $0204
			0x00,
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x85, 0x09,			// [3] STA $09
			0x85, 0x09,			// [3] STA $09
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x85, 0x09,			// [3] STA $09
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x85, 0x09,			// [3] STA $09
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x86, 0x09,			// [3] STX $09
			0x87, 0x09,			// [3] SAX $09
			0x60,				// [6] RTS
		]
		machine.addTrapAddress(0x0203)
		runTest(code, expectedRunLength: 66)
	}

	func testNOP() {
		let code: [UInt8] = [
			0x04, 0x00,	// [3] NOP zpg
			0x14, 0x00,	// [4] NOP zpg, x
			0x34, 0x00,	// [4] NOP zpg, x
			0x44, 0x00,	// [3] NOP zpg
			0x54, 0x00,	// [4] NOP zpg, x
			0x64, 0x00,	// [3] NOP zpg
			0x74, 0x00,	// [4] NOP zpg, x
			0x80, 0x00,	// [2] NOP #
			0x82, 0x00,	// [2] NOP #
			0x89, 0x00,	// [2] NOP #
			0xc2, 0x00,	// [2] NOP #
			0xd4, 0x00,	// [4] NOP zpg, x
			0xe2, 0x00,	// [2] NOP #
			0xf4, 0x00,	// [4] NOP zpg, x
		]
		runTest(code, expectedRunLength: 43)
	}

	private func runTest(_ code: [UInt8], expectedRunLength: UInt32) {
		machine.trapHandler = self

		let immediateCode = Data(code)
		machine.setData(immediateCode, atAddress: 0x200)
		machine.addTrapAddress(UInt16(0x200 + code.count))
		machine.setValue(0x00, forAddress: 0x0000)
		machine.setValue(0x00, forAddress: 0x0001)
		machine.setValue(0xff, forAddress: 0x0002)
		machine.setValue(0x00, forAddress: 0x0003)
		machine.setValue(0x08, forAddress: 0x0004)
		machine.setValue(0x02, forAddress: 0x0005)
		machine.setValue(0x200, for: CSTestMachine6502Register.programCounter)
		machine.setValue(0xff, for: CSTestMachine6502Register.X)
		machine.setValue(0xfe, for: CSTestMachine6502Register.Y)

		endTime = 0
		while endTime == 0 {
			machine.runForNumber(ofCycles: 10)
		}

		XCTAssertEqual(endTime, expectedRunLength, "Took \(endTime) cycles to perform rather than \(expectedRunLength)")
	}

	func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) {
		if endTime == 0 {
			endTime = (machine.timestamp / 2) - 1
		}
	}
}
